﻿/* Copyright (C)
 * 2019 - Hu Ren, rh890127a@163.com
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
/**
 * @file mpiWrapper.hpp
 * @brief
 * global interfaces for communication
 * @author Hu Ren, rh890127a@163.com
 * @version v0.1
 * @date 2019-08-04
 */

#ifndef UTILITY_MPI_WRAPPER_HPP
#define UTILITY_MPI_WRAPPER_HPP

#include "utilityCommunicationManager.hpp"
#include "utilityCommunicator.hpp"

// using namespace HSF;

extern "C" void initUtility(int *argc, char ***argv);
namespace UTILITY {
/**
 * @brief COMM
 *        Communication entrance, enable flexibility by generating different
 * global communication manager object.
 *
 */
class COMM {
  friend void ::initUtility(int *argc, char ***argv);

 private:
  /** @brief static global communication manager */
  static CommunicationManager *globalManager;
  static bool createLogFile_;

  /** @brief private defult constructor preventing explicit instantiation */
  COMM() {}

  /**
   * @brief init
   * initialize global communication environment
   * @param[in] argc parameters from main
   * @param[in] argv parameters from main
   * @param[in] type the communication environment type, default is MPI
   */
  static void init(int *argc, char ***argv, const char *type = "MPI");

 public:
  /**
   * @brief stop
   * stop and clean global communication environment
   */
  static void stop();

  /**
   * @brief whether create log file or not
   */
  static void createLogFile(bool TorF);

  //--------------------------------------------------------------
  // complex communicator creation and manipulation
  //--------------------------------------------------------------

  /**
   * @brief duplicate
   * Copy constructor.
   *
   * @param[in] refComm reference communicator
   * @param[out] newComm a copy of the reference communicator
   * @return
   */
  static inline int duplicate(const Communicator &refComm,
                              Communicator &newComm) {
    return globalManager->duplicate(refComm, newComm);
  }

  /**
   * @brief split
   * split the referenced communicator to non-overlaping sub-communicators.
   *
   * @param[in] refComm the reference communicator on which to do splitting
   * @param[out] newComm the new communicator generated by splitting
   * @param[in] color tag to identify the grouping rule.
   * @param[in] nPart the number of new communicators, used only for result
   * checking
   * @param[in] priority priority to determine the process id, defult is 1.
   * @return
   */
  static inline int split(const Communicator refComm, Communicator newComm,
                          const int color, const int nPart,
                          const int priority = 1) {
    return globalManager->split(refComm, newComm, color, nPart, priority);
  }

  /**
   * @brief split
   * extract a communicator to from the reference communicator.
   *
   * @param[in] refComm the reference communicator on which to do extraction
   * @param[out] newComm the new communicator generated by extraction
   * @param[in] color tag to identify whether local process will participate in
   * the new communicator or not.
   * checking
   * @param[in] priority priority to determine the process id, defult is 1.
   * @return
   */
  static inline int extract(const Communicator &refComm, Communicator &newComm,
                            const bool color, int priority = 1) {
    return globalManager->split(refComm, newComm, color, priority);
  }

  /**
   * @brief reBalance
   * calculate rebalance solution of the input communicators ( from different
   * processes)
   *
   * @param[in] oldComm The local communicator to be rebalanced with other
   * remote communicators All the input communicators must have NO
   * intersections with each other.
   * @param[out] newComm The new local communicator generated with better load
   * balance.
   * @param[in] weight non-negetive integer representing the weight of local
   * process
   * @param[in] thresholdRatio ratio to ideal load balance (100%), non-negative
   * @return
   */
  static inline int reBalance(const Communicator &oldComm,
                              Communicator &newComm, const int weight,
                              const float thresholdRatio) {
    return globalManager->reBalance(oldComm, newComm, weight, thresholdRatio);
  }

  // TODO, Cartesian-topology communicator constructor
  static inline int createCart(const Communicator &refComm,
                               Communicator &newComm, const int ndims,
                               const int *dims, int *periods, int reorder = 1) {
    return globalManager->createCart(refComm, newComm, ndims, dims, periods,
                                     reorder);
  }

  // TODO, graph-topology communicator constructor
  static inline int createGraph(const Communicator &refComm,
                                Communicator &newComm, const int degree,
                                const int *dests, const int *weights,
                                const int reorder = 1) {
    return globalManager->createGraph(refComm, newComm, degree, dests, weights,
                                      reorder);
  }

  //--------------------------------------------------------------
  // Access
  //--------------------------------------------------------------
  static inline bool isParallel() { return globalManager->isParallel(); }

  static inline CommunicationManager &getCommManager() {
    return *globalManager;
  }

  static inline Communicator &getGlobalComm() {
    return globalManager->getGlobalComm();
  }

  static inline int getGlobalId() { return globalManager->getGlobalId(); }

  static inline int getGlobalSize() { return globalManager->getGlobalSize(); }

  /**
   * @brief calculateBalance
   * calculate the balance ratio of the input communicators ( from different
   * processes)
   *
   * @param[in] refComm The local communicator to be rebalanced with other
   * remote communicators All the input communicators must have NO
   * intersections with each other.
   * @param[in] weight non-negetive integer representing the weight of local
   * process
   * @return current ratio to ideal load balance (100%), non-negative
   */
  static inline float calculateBalance(const Communicator &refComm,
                                       const int weight) {
    return globalManager->calculateBalance(refComm, weight);
  }

  //--------------------------------------------------------------
  // parallel standard I/O
  //--------------------------------------------------------------
  /**
   * @brief pOut
   * return an IO object that is pre-connected with a log file of the current
   * process.
   *
   * @return
   */
  static inline OStream &pOut() { return globalManager->pOut(); }

  /**
   * @brief pErr
   * return a multi-file IO object that is pre-connected with screen output and
   * process log file
   * @return
   */
  static inline OStream &pErr() { return globalManager->pErr(); }

  static inline std::ostream &cOut() {
    return (globalManager->getGlobalId() == 0 ? (std::cout)
                                              : globalManager->cDummyOut());
  }
  static inline std::ostream &cErr() {
    return (globalManager->getGlobalId() == 0 ? (std::cerr)
                                              : globalManager->cDummyOut());
  }
};

//--------------------------------------------------------------
// simplified parallel standard I/O
//--------------------------------------------------------------

// macro for parallel output to file linked with each process, eg. "POUT<<"
#define POUT COMM::pOut()

// macro for error output to file linked with the failed process and stdout,
#define PERR COMM::pErr()
/*
eg. "PERR<<"
#define PERR COMM::pErr()<<"!Proc["<<COMM::getGlobalId()<<"] Error:
"<<"\n\t"\
<<"at "<<__FILE__<<" +"<<__LINE__<<" in function \""<<__FUNCTION__<<"\": "\
<<"\n\t"
*/

// macro for master process to print log to stdout, eg. "COUT<<"
#define COUT COMM::cOut()
#define CERR COMM::cErr()
// #define COUT if (COMM::getGlobalId() == 0) std::cout

// macro for specified process to print log to stdout, eg. "SOUT(1)<<"
#define SOUT(id) \
  if (COMM::getGlobalId() == id) std::cout << "proc[" << id << "]: "

}  // namespace UTILITY

#endif  // std map
